package com.yerbamateprimer.action.user.impl;

import java.io.Serializable;
import java.util.List;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.yerbamateprimer.action.user.SynchronousUserAction;
import com.yerbamateprimer.advice.impl.DeliverTheMateAdvice;
import com.yerbamateprimer.advice.impl.DrinkTheMateAdvice;
import com.yerbamateprimer.advice.impl.PrimerChangedAdvice;
import com.yerbamateprimer.model.User;
import com.yerbamateprimer.round.Round;
import com.yerbamateprimer.service.ClientService;
import com.yerbamateprimer.service.RoundService;

/**
 * This action is useful to "say"
 * i'm not drinking more mate.
 * 
 * @author Matias Liwski
 * @version 1.0
 */
public class LeaveRoundUserAction implements SynchronousUserAction, Serializable {
	private static final Logger LOGGER = LoggerFactory.getLogger(LeaveRoundUserAction.class);
	private static final long serialVersionUID = 8344967091801110807L;
	private User user;
	private transient RoundService roundService;
	private transient ClientService clientService;

	/**
	 * Default constructor, used by the serializer.
	 */
	public LeaveRoundUserAction() {
	}

	/**
	 * @param user The user that want to execute this action.
	 */
	public LeaveRoundUserAction(User user) {
		setUser(user);
	}

	@Override
	public void execute() {
		checkArgumentPreconditions();
		Round round = this.roundService.getRoundForUser(this.user);
		Preconditions.checkState(round != null, "The user: " + this.user + " isn't participating at any round.");
		Preconditions.checkState(round.getDrinkers().contains(user), "The user: " + this.user + " is only invited to the round: " + round + ".");
		
		round.removeUser(this.user);
		this.roundService.unregisterUserFromRound(this.user);
		
		List<User> drinkers = round.getDrinkers();
		if(drinkers.size() > 0) {
			if(this.user.equals(round.getPrimer())) {
				User newPrimer = drinkers.get(0);
				round.setPrimer(newPrimer);
				this.clientService.send(new PrimerChangedAdvice(drinkers, newPrimer));
			}
			
			if(this.user.equals(round.getCurrentDrinker())) {
				User newDrinker = round.rotateCurrentDrinker();
				this.clientService.send(new DeliverTheMateAdvice(this.user, newDrinker));
				this.clientService.send(new DrinkTheMateAdvice(newDrinker, this.user));
			}
		} else {
			round.setLastIntendedAction(this);
			round.getCurrentState().getOut(round);
		}
		
		LOGGER.debug("Users after {} leave the round: {} ", this.user, drinkers);
	}

	/**
	 * Check the preconditions of the arguments needed to execute this action.
	 */
	private void checkArgumentPreconditions() {
		Preconditions.checkArgument(this.user != null, "This action need a user to execute it.");
		Preconditions.checkArgument(this.roundService != null, "This action need a round service to execute it.");
		Preconditions.checkArgument(this.clientService != null, "This action needs a client service to execute it.");
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.action.user.UserAction#setUser(com.yerbamateprimer.model.User)
	 */
	@Override
	public void setUser(User user) {
		Preconditions.checkArgument(user != null, "This action needs a user to execute it.");
		this.user = user;
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.action.user.UserAction#getUser()
	 */
	@Override
	public User getUser() {
		return this.user;
	}

	/**
	 * @param roundService The round service of the Action, to set.
	 */
	public void setRoundService(RoundService roundService) {
		Preconditions.checkArgument(roundService != null, "This action needs a round service to execute it.");
		this.roundService = roundService;
	}

	/**
	 * @return The roundService of the Action.
	 */
	public RoundService getRoundService() {
		return this.roundService;
	}

	/**
	 * @param clientService The client service of the Action, to set.
	 */
	public void setClientService(ClientService clientService) {
		Preconditions.checkArgument(clientService != null, "This action needs a client service to execute it.");
		this.clientService = clientService;
	}

	/**
	 * @return The client service of the Action.
	 */
	public ClientService getClientService() {
		return this.clientService;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE, false, true);
	}
}